package com.hfi.ood.litejunit.v3.framework.runners;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

/**
 * @author ChangLiang
 * @date 2020/5/28
 */
public abstract class BeforeAndAfterRunner {

    private static class FailedBefore extends Exception{

    }

    private final Class<? extends Annotation> beforeAnnotation;

    private final Class<? extends Annotation> afterAnnotation;

    private TestIntrospector testIntrospector;

    private Object test;

    public BeforeAndAfterRunner(
            Class<?> testClass,
            Class<? extends Annotation> beforeAnnotation,
            Class<? extends Annotation> afterAnnotation,
            Object test) {
        this.beforeAnnotation = beforeAnnotation;
        this.afterAnnotation = afterAnnotation;
        this.testIntrospector = new TestIntrospector(testClass);
        this.test = test;
    }

    public void runProtected() {
        try {
            runBefores();
            runUnprotected();
        } catch (FailedBefore failedBefore) {

        } finally {
            runAfters();
        }

    }

    protected abstract void runUnprotected();

    protected abstract void addFailure(Throwable targetException);

    /**
     * Stop after first failed @Before
     * @throws FailedBefore
     */
    private void runBefores() throws FailedBefore{
        List<Method> befores = testIntrospector.getTestMethods(beforeAnnotation);
        for (Method method : befores) {
            try {
                invokeMethod(method);
            } catch (InvocationTargetException e) {
                addFailure(e.getTargetException());
                throw new FailedBefore();
            } catch (IllegalAccessException e) {
                addFailure(e);
                throw new FailedBefore();
            }
        }
    }

    /**
     * Try to run all @Afters regardless
     */
    private void runAfters() {
        List<Method> afters = testIntrospector.getTestMethods(afterAnnotation);
        for (Method method : afters) {
            try {
                invokeMethod(method);
            } catch (InvocationTargetException e) {
                addFailure(e.getTargetException());
            } catch (IllegalAccessException e) {
                addFailure(e);
            }
        }
    }

    private void invokeMethod(Method method) throws InvocationTargetException, IllegalAccessException {
        method.invoke(test);
    }
}
